package com.qyer.commons.persistence;

import static com.qyer.commons.enums.SourceStatus.INITIAL;
import static com.qyer.commons.enums.SourceStatus.NOT_AVAILABLE;
import static com.qyer.commons.enums.SourceStatus.STANDBY;

import com.qyer.commons.enums.SourceStatus;
import com.qyer.commons.exception.RedisException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.List;

/**
 * User: Z J Wu Date: 2015/06/01 Time: 10:15 Package: com.qyer.ra.commons.misc
 */
public abstract class AbstractRedisOperator {

  private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRedisOperator.class);

  private String EMPTY_PLACEHOLDER = "EMPTY";
  private String[] EMPTY_LIST_PLACEHOLDER = new String[]{EMPTY_PLACEHOLDER};

  private JedisPool pool;
  protected SourceStatus sourceStatus;

  public SourceStatus getSourceStatus() {
    return sourceStatus;
  }

  public void initByLocalConf(String confPath) throws ConfigurationException {
    CompositeConfiguration c = new CompositeConfiguration();
    c.addConfiguration(new PropertiesConfiguration(confPath));
    setPool(c);
  }

  public void destory() {
    if (this.pool != null) {
      this.pool.close();
    }
  }

  public synchronized void setPool(CompositeConfiguration c) {
    this.sourceStatus = INITIAL;
    try {
      this.pool = buildJedisPoolFromConfiguration(c);
      this.sourceStatus = STANDBY;
      LOGGER.info("Redis pool inited.");
    } catch (Exception e) {
      this.sourceStatus = NOT_AVAILABLE;
    }
  }

  public JedisPool buildJedisPoolFromConfiguration(CompositeConfiguration c) throws RedisException {
    if (c == null || c.isEmpty()) {
      throw new RedisException("Configuration is empty.");
    }
    String host = c.getString("redis.host");
    int port = c.getInt("redis.port");
    int maxTotal = c.getInt("redis.maxConn");
    int maxIdle = c.getInt("redis.maxIdle");
    int maxWait = c.getInt("redis.maxWait");
    boolean testWhileIdle = c.getBoolean("redis.testWhileIdle");
    LOGGER.info("RedisInfo - host - {}", host);
    LOGGER.info("RedisInfo - port - {}", port);
    LOGGER.info("RedisInfo - maxTotal - {}", maxTotal);
    LOGGER.info("RedisInfo - maxIdle - {}", maxIdle);
    LOGGER.info("RedisInfo - maxWait - {}", maxWait);
    LOGGER.info("RedisInfo - testWhileIdle - {}", testWhileIdle);
    return buildJedisPool(host, port, maxTotal, maxIdle, maxWait, testWhileIdle);
  }

  public JedisPool buildJedisPool(String host, int port, int maxTotal, int maxIdle, int maxWait,
                                  boolean testWhileIdle) {
    JedisPoolConfig poolConfig = new JedisPoolConfig();
    poolConfig.setMaxTotal(maxTotal);
    poolConfig.setMaxIdle(maxIdle);
    poolConfig.setMaxWaitMillis(maxWait);
    poolConfig.setTestWhileIdle(testWhileIdle);
    return new JedisPool(poolConfig, host, port);
  }

  private void returnResource(Jedis jedis) {
    if (jedis != null) {
      pool.returnResourceObject(jedis);
    }
  }

  private void returnRedisObject(Jedis jedis){
    if (jedis != null) {
      jedis.close();
    }
  }

  private Jedis borrow() {
    return pool.getResource();
  }

  private void check(String key) throws RedisException {
    if (StringUtils.isBlank(key)) {
      throw new RedisException("Empty key.");
    }
  }

  public void setList(String key, String values) throws RedisException {
    set(key, values, 0);
  }

  public void setList(String key, List<String> values, int timeout) throws RedisException {
    check(key);
    Jedis jedis = null;
    String[] arr = CollectionUtils.isEmpty(values) ? EMPTY_LIST_PLACEHOLDER
                                                   : values.toArray(new String[values.size()]);
    try {
      jedis = borrow();
      jedis.rpush(key, arr);
      if (timeout >= 0) {
        jedis.expire(key, timeout);
      }
    } catch (Exception e) {
      throw new RedisException(e);
    } finally {
      returnRedisObject(jedis);
    }
  }

  public void set(String key, Object value) throws RedisException {
    set(key, value, 0);
  }

  public void set(String key, Object value, int timeout) throws RedisException {
    check(key);
    Jedis jedis = null;
    try {
      jedis = borrow();
      if(timeout > 0)
        jedis.setex(key, timeout, String.valueOf(value));
      else
        jedis.set(key, String.valueOf(value));
    } catch (Exception e) {
      throw new RedisException(e);
    } finally {
      returnRedisObject(jedis);
    }
  }

  public List<String> getAsList(String key) throws RedisException {
    check(key);
    Jedis jedis = null;
    try {
      jedis = borrow();
      List<String> result = jedis.lrange(key, 0, -1);
      if (CollectionUtils.isEmpty(result) || (result.size() == 1 && EMPTY_PLACEHOLDER
        .equals(result.get(0))
      )) {
        return null;
      }
      return result;
    } catch (Exception e) {
      throw new RedisException(e);
    } finally {
      returnRedisObject(jedis);
    }
  }

  public String get(String key) throws RedisException {
    check(key);
    Jedis jedis = null;
    try {
      jedis = borrow();
      return jedis.get(key);
    } catch (Exception e) {
      throw new RedisException(e);
    } finally {
      returnRedisObject(jedis);
    }
  }

  public String ping() throws RedisException {
    Jedis jedis = null;
    String pingResult;
    try {
      jedis = borrow();
      pingResult = jedis.ping();
      LOGGER.info("Redis ping result - {}", pingResult);
      return pingResult;
    } catch (Exception e) {
      throw new RedisException(e);
    } finally {
      returnRedisObject(jedis);
    }
  }

}
